package com.sdy.healthcode.biz.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.sdy.common.model.BizException;
import com.sdy.common.utils.Assert;
import com.sdy.common.utils.DateUtil;
import com.sdy.common.utils.MapUtil;
import com.sdy.healthcode.biz.constants.Constants;
import com.sdy.healthcode.biz.constants.HealthStatusType;
import com.sdy.healthcode.biz.constants.RedisConstants;
import com.sdy.healthcode.biz.dto.HealthClockInDTO;
import com.sdy.healthcode.biz.model.HealthClockIn;
import com.sdy.healthcode.biz.mapper.HealthClockInMapper;
import com.sdy.healthcode.biz.model.HealthCode;
import com.sdy.healthcode.biz.service.HealthClockInService;
import com.sdy.healthcode.biz.service.HealthCodeService;
import com.sdy.healthcode.biz.utils.ParamChecker;
import com.sdy.mvc.service.impl.BaseServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.integration.redis.util.RedisLockRegistry;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.stream.Collectors;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author zzq
 * @since 2020-02-23
 */
@Slf4j
@Service
public class HealthClockInServiceImpl extends BaseServiceImpl<HealthClockIn> implements HealthClockInService {
    @Autowired
    private HealthClockInMapper healthClockInMapper;
    @Autowired
    private HealthCodeService healthCodeService;
    @Autowired
    private RedisLockRegistry redisLockRegistry;
    @Autowired
    @Lazy
    private HealthClockInService healthClockInService;

    @Override
    public void clockInLockable(HealthClockInDTO healthClockInDTO) throws BizException, InterruptedException {
        Lock lock = redisLockRegistry.obtain(RedisConstants.LOCK_APPLY_HEALTHCODE + healthClockInDTO.getUserId());
        if (!lock.tryLock(10, TimeUnit.SECONDS)) {
            throw new BizException("操作过于频繁，请稍后再试。");
        }
        try {
            healthClockInService.clockIn(healthClockInDTO);
        } finally {
            lock.unlock();
        }
    }

    @Override
    @Transactional(transactionManager = "transactionManager", propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
    public void clockIn(HealthClockInDTO healthClockInDTO) throws BizException {
        // 只能打卡今天，但是可以重复打卡
        healthClockInDTO.setClockDate(DateUtil.getDateBegin(new Date()));
        Assert.isTrue(!ParamChecker.checkParamRequired(healthClockInDTO.getHealthStatus(), Arrays.stream(HealthStatusType.values()).map(HealthStatusType::getType).toArray()), "参数<健康状态>错误");
        Assert.isTrue(!ParamChecker.checkParamRequired(healthClockInDTO.getHealthNearInfect(), Constants.YES_CN, Constants.NO_CN), "参数<是否邻近确诊病人>错误");
        Assert.isTrue(!ParamChecker.checkParamRequired(healthClockInDTO.getHealthTouchInfect(), Constants.YES_CN, Constants.NO_CN), "参数<是否接触确诊病人>错误");
        Assert.isTrue(!ParamChecker.checkParamRequired(healthClockInDTO.getHealthTouchSuspect(), Constants.YES_CN, Constants.NO_CN), "参数<是否接触湖北等地的人>错误");
        Assert.isTrue(!ParamChecker.checkParamRequired(healthClockInDTO.getClockDate()), "参数<打卡日期>错误");
        HealthCode healthCode = healthCodeService.queryHealthCode(healthClockInDTO.getUserId());
        Assert.isNull(healthCode, "健康码不存在。");
        HealthClockIn healthClockIn = new HealthClockIn()
                .setUserId(healthClockInDTO.getUserId())
                .setHealthStatus(healthClockInDTO.getHealthStatus())
                .setHealthNearInfect(healthClockInDTO.getHealthNearInfect())
                .setHealthTouchInfect(healthClockInDTO.getHealthTouchInfect())
                .setHealthTouchSuspect(healthClockInDTO.getHealthTouchSuspect())
                .setClockDate(healthClockInDTO.getClockDate())
                .setHealthCodeId(healthCode.getId())
                .setCreateTime(new Date());
        boolean hadClockId = lambdaQuery()
                .eq(HealthClockIn::getUserId, healthClockInDTO.getUserId())
                .eq(HealthClockIn::getClockDate, healthClockIn.getClockDate())
                .count() > 0;
        if (hadClockId) {
            remove(new LambdaQueryWrapper<HealthClockIn>()
                    .eq(HealthClockIn::getUserId, healthClockInDTO.getUserId())
                    .eq(HealthClockIn::getClockDate, healthClockIn.getClockDate()));
        }
        save(healthClockIn);
        // 更新健康码数据
        healthCodeService.reEvaluateFromClockInData(healthClockIn.getUserId(), healthClockIn);
    }

    @Override
    public HealthClockInDTO getClockIn(Date date, Integer userId) {
        HealthClockIn clockIn = lambdaQuery().eq(HealthClockIn::getUserId, userId).eq(HealthClockIn::getClockDate, date).one();
        if (clockIn == null) {
            return null;
        }
        return new HealthClockInDTO()
                .setClockDate(clockIn.getClockDate())
                .setHealthNearInfect(clockIn.getHealthNearInfect())
                .setHealthStatus(clockIn.getHealthStatus())
                .setHealthTouchInfect(clockIn.getHealthTouchInfect())
                .setHealthTouchSuspect(clockIn.getHealthTouchSuspect());
    }

    @Override
    public List<Date> clockInHistory(Integer userId) {
        List<HealthClockIn> list = lambdaQuery()
                .eq(HealthClockIn::getUserId, userId)
                .select(HealthClockIn::getClockDate)
                .orderByDesc(HealthClockIn::getClockDate)
                .list();
        return list.stream().map(HealthClockIn::getClockDate).collect(Collectors.toList());
    }

    @Override
    public List<HealthClockIn> listClockData(Integer userId, Integer days, Date evaluateDate) {
        Date startDate = DateUtil.addDate(evaluateDate, -(days - 1));
        List<HealthClockIn> l = lambdaQuery()
                .eq(HealthClockIn::getUserId, userId)
                .between(HealthClockIn::getClockDate, startDate, evaluateDate)
                .list();
        Map<String, HealthClockIn> m = MapUtil.collectionToMap(l, d -> DateUtil.formatDate(d.getClockDate()));
        List<HealthClockIn> result = new ArrayList<>();
        for (int i = 0; i < days; i++) {
            result.add(m.get(DateUtil.formatDate(DateUtil.addDate(startDate, days - i - 1))));
        }
        return result;
    }
}
